﻿#include <iostream>
#include "PA.h"

int main()
{
	std::cout << "Hello World!\n";


	int x{ 12 };
	int& ref{ x }; //引用

	//对于使用多个字节内存的对象，& 操作符将返回该对象使用的第一个字节的内存地址。
	//获取地址,内存地址通常打印为十六进制值，通常没有0x前缀
	auto p = &x;

	/*&符号往往会引起混淆，因为它根据上下文具有不同的含义：
	1,当跟在类型名称后面时，& 表示左值引用：int& ref。
	2,在表达式中的一元上下文中使用时，& 是取地址操作：std::cout «& x。
	3,在表达式的二进制上下文中使用时，& 是逐位AND运算符：std::cout « x& y。*/


	std::cout << "原值：" << x << "\n";
	std::cout << "引用值：" << ref << "\n";
	std::cout << "地址：" << p << "\n";


	//访问存储在该地址的值使用解引用运算符 *
	int x2 = *p; //获取指针p地址对应的值
	std::cout << "地址：" << p << "对应的值为：" << x2 << "\n";

	//运算符（&）和解引用运算符（*）是相反的：& 获取对象的地址，而解引用获取地址处的对象。



	//指针是保存内存地址（通常是另一个变量的地址）作为其值的对象。
	// 这允许我们存储其他对象的地址以供以后使用。
	int x3 = 99;
	int* p2;  //指向int的指针，注意：这个星号是指针声明语法的一部分，而不是解引用操作符的使用


	/*
	与普通变量一样，指针在默认情况下不会初始化。尚未初始化的指针有时称为野指针。
	野指针包含垃圾地址，解引用野指针将导致未定义的行为。因此，您应该始终初始化指针。

	一旦有一个指针保存另一个对象的地址，就可以使用解引用操作符（*）来访问该地址上的值。
	*/

	p2 = &x3; //获取x2的变量地址
	int x2_y = *p2; //从地址取出值


	//就像引用的类型必须匹配被引用对象的类型一样，指针的类型必须与所指向的对象的类型匹配
	//int i{ 5 };
	//double d{ 7.0 };
	//int* iPtr{ &i };     // ok: int指针，指向int
	//int* iPtr2{ &d };   // not okay: int指针不能指向double
	//double* dPtr{ &d };  // ok: double指针，指向double
	//double* dPtr2{ &i }; // not okay: double指针不能指向int

	//不允许使用字面值初始化指针：
	//int* ptr{ 5 }; // not okay
	//int* ptr{ 0x0012FF7C }; // not okay, 0x0012FF7C 被认为是int字面值



	/*指针和赋值
	我们可以以两种不同的赋值方式来使用指针：
	1,改变指针所指向的对象（为指针赋值一个新的所指向的地址）
	2,改变指针指向对象的值（通过解引用操作，为所指对象赋值）*/

	int v1 = 22;
	int* v1_p = &v1; //获取地址
	std::cout << "v1=" << *v1_p << "\n";  //取地址的值

	int v2 = 23;
	v1_p = &v2;//改变指针的指向
	std::cout << "v1=" << *v1_p << "\n";  //取地址的值

	//使用指针来更改所指向的值
	*v1_p = 24;
	std::cout << "v1=" << *v1_p << "\n";  //取地址的值
	std::cout << "v2=" << v2 << "\n";

	/*
	当使用没有解引用（ptr）的指针时，访问指针持有的地址。
	修改它（ptr = &y）将更改指针所指向的地址。

	当解引用指针（ * ptr）时，访问所指向的对象。
	修改它（ * ptr = 6；）将更改所指向对象的值。*/

	//指针和左值引用的行为类似

	std::cout << typeid(&v1).name() << '\n'; // 打印 &v1 的类型


	/*指针的大小取决于编译可执行文件的体系结构——32位可执行文件使用32位内存地址——
	因此，32位机器上的指针是32位（4字节）。对于64位可执行文件，指针将是64位（8字节）。

	指针的大小始终相同。这是因为指针只是内存地址，并且访问内存地址所需的位数是恒定的。
	*/

	char* chPtr{};        // char 1字节
	int* iPtr{};          // int 通常4字节
	long double* ldPtr{}; // long double 通常8或者12个字节

	std::cout << sizeof(chPtr) << '\n'; // 打印 8
	std::cout << sizeof(iPtr) << '\n';  // 打印 8
	std::cout << sizeof(ldPtr) << '\n'; // 打印 8

	/*悬空指针是保存不再有效的对象地址的指针（例如，因为它已被销毁）。
	解引用悬空指针（例如，为了打印所指向的值）将导致未定义的行为，
	因为您正在尝试访问不再有效的对象。

	解引用无效指针将导致未定义的行为。无效指针值的任何其他使用都是由实现定义的。
	*/

	//当指针持有空值时，这意味着指针没有指向任何东西。这样的指针称为空指针。
	int* null_p{};  //空指针，不指定任何对象
	null_p = &v2; //使指针有效
	std::cout << "p=" << *null_p << "\n";


	//如果不是用有效对象的地址初始化指针，则将其置空。

	//可以使用nullptr显式地初始化指针或为指针分配null值。
	null_p = nullptr;
	//std::cout << "p=" << *null_p << "\n"; //解空指针将抛出异常

	//检查空指针
	if (null_p == nullptr)
	{
		std::cout << "null_p是空指针" << "\n";
	}
	else
	{
		std::cout << "null_p不是空指针" << "\n";
	}


	// 隐式转换：空指针转换为布尔值false，非空指针转换成布尔值true
	if (null_p) // 隐式转换为bool值
		std::cout << "null_p不是空指针" << "\n";
	else
		std::cout << "null_p是空指针" << "\n";



	/*
	条件语句只能用于区分空指针和非空指针。
	没有方便的方法来确定非空指针是指向有效对象还是悬空（指向无效对象）。

	当对象被销毁时，指向该对象的任何指针都将悬空。这样的指针不会自动为空！
	程序员有责任确保刚刚被销毁的对象的所有指针都正确设置为nullptr。

	指针应该保存有效对象的地址，或者设置为nullptr。这样，我们只需要测试指针是否为null，
	并且可以假设任何非null指针都是有效的。

	当对象被销毁时，指向被销毁对象的任何指针都将悬空（它们不会自动设置为nullptr）。
	您有责任检测这些情况，并确保随后将这些指针设置为nullptr。

	旧式的空指针字面值：0和NULL
	float* ptr { 0 };  // ptr 是空指针 (样例，实际请不要这样写)

	还有一个名为NULL的预处理器宏（在 cstddef 头文件中定义）
	double* ptr { NULL }; // ptr 是空指针


	尽可能支持引用而不是指针
	指针和引用都能够间接访问其他对象。
	指针具有额外的能力，可以更改它们所指向的内容，并指向null。
	然而，这些指针功能本身也是危险的：空指针有被解引用的风险，
	并且更改指针指向的内容的功能可以使创建悬空指针变得更容易：

	由于引用不能绑定到null，因此我们不必担心null引用。并且，
	由于在创建时引用必须绑定到有效对象，然后不能重新设置，因此悬空引用更难创建。
	因为引用更安全，所以优先使用引用而不是指针，除非需要指针提供的额外功能。
	*/


	/*常量指针是指向常量值的指针。
	要声明指向常量值的指针，请在指针的数据类型之前使用const关键字：
	常量指针无法做修改操作
	*/
	const int ct = 33;
	const int* ct_p = &ct;


	//由于指向常量的指针本身不是常量（它只是指向常量值），
	//因此我们可以通过为指针分配新地址来更改指针所指向的内容：
	const int ct2 = 33;
	ct_p = &ct2;

	//int x{ 5 }; // non-const
	//const int* ptr{ &x }; // ptr 指向 "const int"
	//*ptr = 6;  // 不被允许: ptr 指向 "const int" ，所以不能通过ptr修改指向的对象
	//x = 6;  ok: x是非const，所以仍然可以通过 x 这个标识符来修改实际的对象

	//要声明指针常量，请在指针声明中的星号后面使用const关键字
	int* const const_ptr{ &x }; // 星号后面的 const 代表这是一个 const pointer
	//const_ptr = &ct2;  不允许

	*const_ptr = 6; // okay: 指向的x不是 常量，可以修改

	//指向常量值的指针常量
	//指向常量值的指针常量不能更改其地址，也不能通过指针更改它所指向的值。
	// 它只能解引用以获得它所指向的值。
	const int* const ptr{ &x }; // const指针，指向const对象


	/*可以为非常量指针分配另一个地址，以更改它所指向的对象。
	指针常量始终指向相同的地址，并且不能更改该地址。
	指向非常量的指针可以更改它所指向的值。它不能指向常量。
	指向常量值的指针在通过指针访问时将该值视为常量，因此不能更改它所指向的值。
	指针常量可以指向非常量或常量左值（但不能指向右值，因为右值没有地址）。*/


	/*
	星号之前的const与所指向的类型相关联。因此，这是一个指向常量值的指针，
	不能通过指针修改该值。
	星号后面的const与指针本身相关联。因此，不能为该指针分配新地址。
	*/



	//使用指针函数,传递地址不会复制所指向的对象
	std::string dr = "54786476884";
	printByAddress(&dr);

	//可以时通过引用传递参数，必须时才通过地址传递参数
	//除非您有特定的理由使用指针传递参数，否则首选引用。


	printByAddress2();
	printByAddress2(&dr);

	int qa = 23;
	int* ppp = &qa;
	refP(ppp);


	//使用  引用作为返回值  的方法
	std::cout << getProgramName() << "\n";

	//通过引用返回的对象必须在函数返回后存在
	auto sss = getProgramName2();
	std::cout << sss << "\n";

	/*通过引用返回常量静态局部变量，以简单的方式说明通过引用返回的机制。
	然而，通过引用返回非常量静态局部变量是相当不常见的，通常应该避免。*/


}
